package bj.actor

import scala.actors.Actor
import scala.actors.OutputChannel
import bj.card.Hand
import bj.hkeeping.Ok
import bj.card.Card
import bj.util.Logs
import bj.hkeeping.Broke
import scala.actors.remote.RemoteActor
import scala.actors.remote.RemoteActor._
import scala.actors.remote.Node
import Actor._
import bj.Config

/** DealtCard is sent to the GUI when the player is dealt a card to his/her own hand.
 *  Observe is sent to the GUI when another player at the table is dealt a card */
//case class DealtCard(card : Card)
/** PlayHand is sent to the GUI when it's the player's turn to play */
case class PlayHand(canSplit: Boolean)
/** TableStatus is sent to the GUI when the table status (players,positions) has changed */
case class TableStatus(status : Array[(String,List[Card])])
/** GetTableStatus is sent from the GUI to the HumanPlayer to request table status */
case class GetTableStatus
/** GetTableLimits is sent from the HumanPlayer to the Dealer to request the table min/max */
case class GetTableInfo
/** LeaveTable is sent from the GUI to the HumanPlayer when the leave button is clicked and from the HumanPlayer to the dealer */
case class LeaveTable
/** AssignToTable is sent from the HumanPlayer to the Lobby when the player has joined a table */
case class AssignToTable(remoteGUI : OutputChannel[Any], tableMailbox : OutputChannel[Any], bet : Double)
/** Bet is sent from the PlayerTable to the HumanPlayer when the player has clicked the bet button */
case class PlacedBet(amount : Double)
/** PlayerStatus is sent from HumanPlayer to PlayerTable to update the player's bankroll */
case class PlayerStatus(name : String, bankroll : Double)
/** TableLimits is sent from HumanPlayer to PlayerTable to update the GUI's min/max bet labels */
case class TableInfo(min : Double, max : Double, dealer : String)
/** NotEnoughMoney is sent from HumanPlayer to the GUI to indicate that the player does not have enough money to complete an action */
case class NotEnoughMoney
/** RegisterGUI is sent from the GUI to the HumanPlayer so that HumanPlayer has the GUI's (intermediator's) mailbox */
case class RegisterGUI

class HumanPlayer(playerName: String, beginningBankroll: Double) extends Player(playerName,beginningBankroll,0) with Logs {

  /** My service name */
  val myName = Symbol(pid+"")
      
  val betAmt = 0
  
  // Keep track of which seat this player is sitting in
  var seatNumber = -1
  
    /** House mail box */
  var house : OutputChannel[Any] = null
  
    /** dealer mail box */
  var dealer : OutputChannel[Any] = null
  
    /** GUI mail box */
  var GUI : OutputChannel[Any] = null
  
  /** Indication that when we got our seat number we hadn't registered with the GUI yet and when we do register with the GUI we should send it our
   * seat number
   */
  var sendSeatNumberLater = false
  
  
    /** This method receives messages */
  /** Since this class represents the liaison between the actual human player and the game,
   * the act method needs to send messages to the GUI as messages are received
   * from the dealer and vice versa
   */
  override def act {
     // Declare the port I'm listening on
    alive(Config.PortNumber)

    // Register me
    register(myName, self)
    
    println(">>> started "+myName+" @ "+Config.IPAddress+":"+Config.PortNumber)

    loop {
      react {
        // Receives message to tell player to place its bet
        case Go =>
//          debug(this+" received Go placing bet = "+betAmt+" from bankroll = "+bankroll)
          bet
        // Receives message to tell player to place its bet  
        case PlaceBet =>
          debug(this + " is placing a bet")
          GUI ! PlaceBet
          //dealer ! NewBet(pid,100)
          debug(this + " sent request to GUI")
          
        // Receives indication from the GUI that the player has placed its bet. Sends this bet on to the dealer  
        case PlacedBet(bet) =>
          debug(this + " got bet of " + bet + " from GUI")
          lastBet = bet
          moneyOnTable = bet
          dealer ! NewBet(pid,bet)
          
        // Receives the dealer's up-card and send it on to be displayed by the GUI
        case Up(card) =>
          debug(this + " received dealer's up card = " + card)
          this.upcard = card
          GUI ! Up(card)
          
        // Receives the dealer's down-card and send it to be displayed by the GUI  
        case Hole(card) =>
          GUI ! Hole(card)
         
        // Receives DealerCard indicating that the dealer has hit on his hand. Send the card to be displayed by the GUI  
        case DealerCard(card) =>
          GUI ! DealerCard(card)
        
        // Receive an indication from the Dealer that the player should clear his or her hand in preparation for the next round
        // Don't send this to the GUI. The GUI clears cards from the table when the bet is made for the next round.  
        case ResetHand =>
          hand = List()
          handIndex = 0
        
        // Receive an indication from the Dealer that it's my turn to play. Send this indication on to the GUI to prompt the player to play. 
        case MyTurn =>
          debug(this + " was notified that it is time to act")
          GUI ! PlayHand(hand(handIndex).cards(0).rank == hand(handIndex).cards(1).rank && hand.size == 1)

        // Receives a card from the dealer
        case DealtCard(card,index) => 
          if(GUI != null) {
            if (hand.size == 0) {
              val h = new Object with Hand
              hand = hand ::: List(h)
            }
            hand(handIndex).cards = hand(handIndex).cards ::: List(card)
            
            if (hand(handIndex).doubled) {
                debug("Sending Double Down card to GUI: Seat "+seatNumber+"; Card "+card + " for hand " + index)
                GUI ! ObserveH(card,seatNumber,index)
            }
            else {
              debug("Sending card to GUI: Seat "+seatNumber+"; Card "+card + " for hand " + index)
              GUI ! Observe(card,seatNumber,handIndex)
            }
          }
          if(hand(handIndex).broke || hand(handIndex).doubled)
            handIndex += 1
        
        // Receive a card after a split    
        case SplitCard(card) =>
          hand(1).cards = List(hand(0).cards(1))
          hand(0).cards = List(hand(0).cards(0),card)
          
          GUI ! Observe(card,seatNumber,0)

        // Receives broke message
        case Broke =>
          debug(this+ " received BROKE")
          GUI ! Broke
          
        // Receives message about dealt card
        case Observe(card,seat,index) =>
          debug(this+" observed: "+card + " at seat " + seat + " for hand " + index)
          if (GUI != null) {
            GUI ! Observe(card,seat,index)
          }
         
        // Receive message about a card dealt after the player has doubled down. This card will be displayed horizontally  
        case ObserveH(card,seat,index) =>
          debug(this+" observed Double Down: "+card + " at seat " + seat + " for hand " + index)
          if (GUI != null) {
            GUI ! ObserveH(card,seat,index)
          }
          
        // Receive message indicating that the player in the specified seat has split. Send this to the GUI so it can be shown  
        case ObserveSplit(seat) =>
          debug(this+" observed a Split at seat " + seat)
          GUI ! ObserveSplit(seat)
          
        // Receive a message from the GUI that the player has chosen "Hit" on the GUI. Send this request on to the dealer  
        case Hit(p,x) =>
          dealer ! Hit(pid,handIndex)
        
        // Receive a message from the GUI that the player has chosen "Stand" on the GUI. Send this request on to the dealer   
        case Stay(p,x) =>
          dealer ! Stay(pid,handIndex)
          if(hand(handIndex).didSplit) {
            handIndex += 1
          }
        
        // Receive a message from the GUI that the player has chosen "Double Down" on the GUI. Send this request on to the dealer   
        case DoubleDown(p,x) =>
          if(bankroll >= (moneyOnTable + lastBet)) {
            hand(handIndex).doubled = true
            moneyOnTable += lastBet
            dealer ! DoubleDown(pid,handIndex)
          }
          else {
            GUI ! NotEnoughMoney
          }
         
        // Receive a message from the GUI that the player has chosen to purchase insurance on the GUI. Send this request on to the dealer   
        case Insurance(p) =>
          if(bankroll >= (moneyOnTable + 0.5*lastBet)) {
            moneyOnTable += 0.5*lastBet
            dealer ! Insurance(pid)
          }
          else {
            GUI ! NotEnoughMoney
          }
          
        // Receive a message from the GUI that the player has chosen "Surrender" on the GUI. Send this request on to the dealer   
        case Surrender(p,x) =>
          dealer ! Surrender(pid,handIndex)
          if(hand(handIndex).didSplit) {
            handIndex += 1
          }
        
        // Receive a message from the GUI that the player has chosen "Split" on the GUI. Send this request on to the dealer   
        case Split(p) =>
          if(bankroll >= (moneyOnTable + lastBet)) {
            val hand2 = new Object with Hand
            hand = hand ::: List(hand2)
            hand(0).didSplit = true
            hand(1).didSplit = true
          
            moneyOnTable += lastBet
            
            dealer ! Split(pid)
          }
          else {
            GUI ! NotEnoughMoney
          }
          
        // Receives the table number I've been assigned to
        case TableNumber(tid : Int) =>
          debug(this+" received table assignment tid = "+tid)
          assign(tid)
        
          // Receive a message notifying me that I won.  Add the win amount to the bankroll and send a message to the GUI
        case Win(gain) =>
          val won = lastBet * gain
          
          currentBankroll += won
          
          debug(this+" received WIN " + won + " new bankroll = "+bankroll)
          GUI ! PlayerStatus(this.name,currentBankroll)
          GUI ! Win(currentBankroll)
         
        // Receive a message notifying me that I lost.  Subtract the loss amount from the bankroll and send a message to the GUI  
        case Lose(gain) =>
          val lost = lastBet * gain
          currentBankroll += lost
          debug(this+" received LOSE " + lost + " new bankroll = "+bankroll) 
          GUI ! PlayerStatus(this.name,currentBankroll)
          GUI ! Lose(currentBankroll)
        
          // Receive a message notifying me that I pushed.  Send a message to the GUI  
        case Push(gain) =>
          debug(this+" received PUSH bankroll = "+bankroll)
          GUI ! PlayerStatus(this.name,currentBankroll)
          GUI ! Push(currentBankroll)
        
        // The dealer's showing an Ace and has offered insurance.  Send a message to the GUI  
        case OfferInsurance =>
          debug(this+ "Dealer has Ace showing and is offering insurance")
          GUI ! OfferInsurance
          
        // 3 seconds have elapsed and the dealer has closed insurance. Send a message to the GUI  
        case InsuranceClosed =>
          debug(this+ "Insurance is now closed")
          GUI ! InsuranceClosed
         
        // Receive notification that we missed the timing window and though the insurance button hadn't been disabled yet,
        // by the time your request got to the dealer insurance was no longer being offered.  This will be shown in the GUI
        // by appending a line to the patter. 
        case CloseInsurance =>
          GUI ! SendMsg("You missed the insurance window")
        
        // Dealer dealt and checked his own cards and found he had blackjack. This hand will not be played  
        case DealerBJ =>
          debug(this + "dealer got a blackjack)" )
          GUI ! DealerBJ
        
        // I don't remember exactly what this does but am scared to comment it out. Sooo ....   
        case MinMaxBets(min,max) =>
          minBet = min
          maxBet = max
          
          // Receives an ACK
        case Ok =>
          debug(this + " received Ok")

        // Receive notification that I've been registered to sit at the table. Remember my seat number.  
        // Save the sender as the dealer mailbox so I can talk to him later.
        // If I have a mailbox for the GUI (remote) tell the GUI I've registered so he knows who I am.  If not, 
        // remember to check in with the GUI and send the seat number when he gets around to talking to me.    
        case Registered(seat) =>
          debug(this + "is registered to sit at seat " + seat)
          seatNumber = seat
          
          dealer = sender
          if (GUI != null)
            GUI ! Registered(seat)
          else {
            sendSeatNumberLater = true
            seatNumber = seat
          }
         
        // Receive the first notification from the GUI.  Keep his mailbox so I can talk to him later.  
        // If I've already heard from the dealer and am registered at my table, tell the GUI so and 
        // give him the seat number.  
        case RegisterGUI =>
          GUI = sender
          if (sendSeatNumberLater) {
            GUI ! Registered(seatNumber)
            sendSeatNumberLater = false
          }
        
        // Receive a request from the GUI to get the table status. Send the GUI my name and current bankroll,
        // and forward the request for table info to the dealer  
        case GetTableStatus => 
          
          GUI ! PlayerStatus(this.name, this.bankroll)
          
          dealer ! GetTableInfo
          dealer ! GetTableStatus
        
        // Receive a response to my request for table status from the dealer. Forward it on to the GUI  
        case TableStatus(status) =>
          GUI ! TableStatus(status)
        
        // Receive a response to my request for table status from the dealer. Forward it on to the GUI    
        case TableInfo(min,max,dealer) =>
          GUI ! TableInfo(min,max,dealer)
        
        // Receive a message from the house that the player has tried to join a table by clicking the Join button in the lobby
        // Send this request on to the table, and tell the PlayerTable gui that I joined a table.  
        case AssignToTable(gui, table, bet) =>
          // need to try to join the table here. This message comes from the House and is triggered when the player clicks "join"
          debug("HumanPlayer: joining table")
          table ! ArriveHuman(this.pid)
          gui ! JoinedTable(myName)
          tableId = table.asInstanceOf[Table].tableID
          // if table cannot be joined,
          //sender ! SendMsg("Cannot join table")
          
        // Receives a request from the Human to leave the table
        case LeaveTable =>
          dealer ! LeaveTable
          
        // Receives something completely from left field
        case dontKnow =>
          // Got something we REALLY didn't expect
          debug(this+" received unexpected: "+dontKnow)
      }
    }
  }
}